﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

using LostLib.Collections.Generic;

namespace LostLib.Programs
{
	/// <summary>
	/// Represents user filter for Assembly versions.
	/// </summary>
	[Serializable]
	public class AssemblyVersionFilter : IFormattable
	{
		/// <summary>
		/// Builds dependency tree for given AssemblyInfos.
		/// </summary>
		/// <param name="db">Database to get information from.</param>
		/// <param name="AssemblyFilter">Filter to specify AssemblyInfos.</param>
		/// <param name="tree">Target tree.</param>
		/// <returns>Last root of tree hierarchy.</returns>
		public AssemblyVersion BuildDependencyTree(
			ApplicationsDatabaseDataContext db,
			AssemblyVersionFilter AssemblyFilter,
			VersionDependencyTree tree)
		{
			#region поиск установленной версии
			var versions = 
				AssemblyFilter.Filter(
				from ver in db.AssemblyVersions
				select ver);

			AssemblyVersion version = null;
			foreach(var ver in versions)
				if (ver.Installed)
				{
					if (version == null) version = ver;
					else if (version.Version < ver.Version) version = ver;
				}

			if (version != null) return version;
			#endregion поиск установленной версии

			//нет установленной версии:

			#region поиск нужного пакета
			var pack = (from Assembly in db.AssemblyInfos
						where Assembly.AssemblyName == AssemblyFilter.AssemblyName
						select Assembly).FirstOrDefault();

			if (pack == null) throw new KeyNotFoundException(string.Format(CultureInfo.CurrentCulture, "there is no suitable assembly for filter {0}", AssemblyFilter));
			#endregion поиск нужного пакета
			//pack = нужный пакет

			#region поиск подходящей версии
			version = pack.LatestSuitable;
			if (version == null) throw new KeyNotFoundException(string.Format(CultureInfo.CurrentCulture, "there is no suitable version for filter {0}", AssemblyFilter));

			if (tree.ContainsKey(version)) return version;
			#endregion поиск подходящей версии
			//version = подходящая не установленная и ранее не просмотренная версия

			var list = new List<AssemblyVersion>();
			tree.Add(version, list);

			var dependancies = from dependency in version.VersionDependancies
							   where dependency.DependencyType == "newer"
							   select dependency;

			if (version.VirtualAssembly)
			{
				var suitable = null as VersionDependency;
				foreach (var dependency in dependancies)
				{
					var Assembly = dependency.AssemblyInfo;
					if (Assembly == null) throw new KeyNotFoundException(pack.AssemblyName);

					var installed = Assembly.InstalledVersion;
					if ((installed != null) && (installed.Version >= dependency.Version))
						return installed;

					if (suitable == null)
						suitable = Assembly.LatestSuitable == null? null: dependency;
				}

				if (suitable == null) throw new KeyNotFoundException(string.Format(CultureInfo.CurrentCulture, "there is no suitable version for virtual Assembly {0}", version.AssemblyName));


				var versionDep =
					BuildDependencyTree(db,
						new AssemblyVersionFilter() {
							AssemblyName = suitable.AssemblyName,
							Version = suitable.Version,
						}, tree);
				list.Add(versionDep);

				return version;
			} else
				foreach (var dependency in dependancies)
				{
					var versionDep =
						BuildDependencyTree(db,
							new AssemblyVersionFilter() {
								AssemblyName = dependency.AssemblyName,
								Version = dependency.Version,
							}, tree);

					if (!versionDep.Installed)
						list.Add(versionDep);
				}
			return version;
		}

		/// <summary>
		/// Converts the specified string representation of version filter into AssemblyVersionFilter object.
		/// </summary>
		/// <param name="input">Input string.</param>
		/// <param name="formatProvider">Format provider.</param>
		/// <returns>AssemblyVersionFilter object.</returns>
		public static AssemblyVersionFilter Parse(string input, IFormatProvider formatProvider)
		{
			string[] namever = input.Split(new string[] { "newer" }, StringSplitOptions.None);
			string name = namever[0];
			Version ver = namever.Length == 1 
				? new Version() 
				: new Version(namever[1]);

			var result = new AssemblyVersionFilter() {
				AssemblyName = name,
				Version = ver,
			};
			return result;
		}

		/// <summary>
		/// Required Assembly name.
		/// </summary>
		public string AssemblyName { get; set; }
		/// <summary>
		/// Minimum Assembly version.
		/// </summary>
		public Version Version { get; set; }

		/// <summary>
		/// Filters list of versions.
		/// </summary>
		/// <param name="versions">Version list.</param>
		/// <returns>Filtered IQueryable.</returns>
		public IQueryable<AssemblyVersion> Filter(IQueryable<AssemblyVersion> versions)
		{
			return !Version.IsAny()
				? from version in versions
				  where
					  (version.AssemblyName == AssemblyName)
					  && ((version.Major > Version.Major)
					  || ((version.Major == Version.Major) && (version.Minor > Version.Minor))
					  || ((version.Major == Version.Major) && (version.Minor == Version.Minor) && (version.Build > Version.Build))
					  || ((version.Major == Version.Major) && (version.Minor == Version.Minor) && (version.Build == Version.Build) && (version.Revision >= Version.Revision)))
				  select version
				: from version in versions
				  where
					  (version.AssemblyName == AssemblyName)
				  select version;


		}

		#region IFormattable Members
		/// <summary>
		/// Converts the specified AssemblyVersionFilter to its string representation.
		/// </summary>
		/// <returns>String representation of AssemblyVersionFilter.</returns>
		public override string ToString()
		{
			if (!Version.IsAny())
				return string.Format(CultureInfo.CurrentCulture,
					"{0}>={1}",
					AssemblyName,
					Version);
			else
				return string.Format(CultureInfo.CurrentCulture, "{0}",
					AssemblyName);
		}

		/// <summary>
		/// Converts the specified AssemblyVersionFilter to its string representation.
		/// </summary>
		/// <param name="format">Format.</param>
		/// <param name="formatProvider">Format provider.</param>
		/// <returns>String representation of AssemblyVersionFilter.</returns>
		public string ToString(string format, IFormatProvider formatProvider)
		{
			if (!Version.IsAny())
				return string.Format(formatProvider,
					"{0}>={1}",
					AssemblyName,
					Version);
			else
				return string.Format(formatProvider,
					"{0}",
					AssemblyName);
		}

		#endregion
	}
}
